﻿using CNative.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace CNative.Dapper.Utils
{
    #region class CodeFirstTemplate
    public class CodeFirstTemplate
    {
        #region Template
        public static string ClassTemplate = "{using}\r\n" +
            @"/********************************************************************************
** 类名称：{ClassName}" + @"
* * 描述： {TableName}的实体类 {ClassName}" + @"
* * 作者： {Creator}" + @"
** 创建时间： " + DateTime.Now.ToString("yyyy-MM-dd") + @"
** 最后修改人：
** 最后修改时间：
** 版权所有 (C) :CNative
*********************************************************************************/
" +
                                               "namespace {Namespace}\r\n" +
                                               "{\r\n" +
                                               "{ClassDescription}{TableMap}\r\n" +
                                                ClassSpace + "public partial class {ClassName}\r\n" +
                                                ClassSpace + "{\r\n" +
                                                PropertySpace + "public {ClassName}(){\r\n\r\n" +
                                                "{Constructor}\r\n" +
                                                PropertySpace + "}\r\n" +
                                                "{PropertyName}\r\n" +
                                                 ClassSpace + "}\r\n" +
                                                "}\r\n";
        public static string ClassDescriptionTemplate =
                                                ClassSpace + "///<summary>\r\n" +
                                                ClassSpace + "///{ClassDescription}" +
                                                ClassSpace + "///</summary>";

        public static string PropertyTemplate = PropertySpace + "{DbFieldMap}\r\n" +
                                                PropertySpace + "public {PropertyType} {PropertyName} {get;set;}\r\n";

        public static string PropertyDescriptionTemplate =
                                                PropertySpace + "/// <summary>\r\n" +
                                                PropertySpace + "/// Desc:{PropertyDescription}\r\n" +
                                                PropertySpace + "/// Default:{DefaultValue}\r\n" +
                                                PropertySpace + "/// Nullable:{IsNullable}\r\n" +
                                                PropertySpace + "/// </summary>";

        public static string ConstructorTemplate = PropertySpace + " this.{PropertyName} ={DefaultValue};\r\n";

        public static string UsingTemplate = "using System;\r\n" +
                                               "using System.Linq;\r\n" +
                                               "using System.Text;" + "\r\n";
        #endregion

        #region Replace Key
        public const string KeyUsing = "{using}";
        public const string KeyNamespace = "{Namespace}";
        public const string KeyClassName = "{ClassName}";
        public const string KeyIsNullable = "{IsNullable}";
        public const string KeyTableMap = "{TableMap}";
        public const string KeyConstructor = "{Constructor}";
        public const string KeyDbFieldMap = "{DbFieldMap}";
        public const string KeyPropertyType = "{PropertyType}";
        public const string KeyPropertyName = "{PropertyName}";
        public const string KeyDefaultValue = "{DefaultValue}";
        public const string KeyClassDescription = "{ClassDescription}";
        public const string KeyPropertyDescription = "{PropertyDescription}";
        public const string KeyTableName = "{TableName}";
        public const string KeyCreator = "{Creator}";
        #endregion

        #region Replace Value
        public const string ValueTableMap = "\r\n" + ClassSpace + "[TableMap(TableName = \"{0}\", Schema = \"{1}\")]";
        public const string ValueSugarCoulmn = "\r\n" + PropertySpace + "[DbFieldMap({0})]";
        #endregion

        #region Space
        public const string PropertySpace = "           ";
        public const string ClassSpace = "    ";
        #endregion
    }
    #endregion
    #region class CodeFirstProvider
    public partial class CodeFirstProvider : ICodeFirst
    {
        public virtual DbHelper Context { get; set; }
        private string ClassTemplate { get; set; }
        private string ClassDescriptionTemplate { get; set; }
        private string PropertyTemplate { get; set; }
        private string PropertyDescriptionTemplate { get; set; }
        private string ConstructorTemplate { get; set; }
        private string UsingTemplate { get; set; }
        private string Namespace { get; set; }
        private string Creator { get; set; }
        private bool IsAttribute { get; set; }
        private bool IsDefaultValue { get; set; }
        private bool IsGuid2tring { get; set; }
        private Func<string, bool> WhereColumnsfunc;
        private string DbName { get; set; }
        /// <summary>
        /// 实体类名前缀
        /// </summary>
        private string EntitySuffix { set; get; } = "Entity_";
        private List<DbTableInfo> TableInfoList { get; set; }

        public CodeFirstProvider()
        {
            this.ClassTemplate = CodeFirstTemplate.ClassTemplate;
            this.ClassDescriptionTemplate = CodeFirstTemplate.ClassDescriptionTemplate;
            this.PropertyTemplate = CodeFirstTemplate.PropertyTemplate;
            this.PropertyDescriptionTemplate = CodeFirstTemplate.PropertyDescriptionTemplate;
            this.ConstructorTemplate = CodeFirstTemplate.ConstructorTemplate;
            this.UsingTemplate = CodeFirstTemplate.UsingTemplate;
        }
        public CodeFirstProvider(DbHelper _Context) : this()
        {
            Context = _Context;
        }
        public CodeFirstProvider(string dbName) : this()
        {
            Context = new DbHelper(dbName);
        }

        public void Init(string dbName)
        {
            DbName = dbName;
            Funs.CacheRemoveAll();
            if (!this.Context.SqlDbProvider.IsAnySystemTablePermissions())
            {
                Check.Exception(true, "Codefirst requires system table permissions");
            }
            this.TableInfoList = this.Context.SqlDbProvider.GetTableInfoList(dbName).DeepClone();
            var viewList = this.Context.SqlDbProvider.GetViewInfoList(dbName).DeepClone();
            if (viewList.HasValue())
            {
                this.TableInfoList.AddRange(viewList);
            }
        }

        #region Setting Template
        public ICodeFirst SettingClassDescriptionTemplate(Func<string, string> func)
        {
            this.ClassDescriptionTemplate = func(this.ClassDescriptionTemplate);
            return this;
        }

        public ICodeFirst SettingClassTemplate(Func<string, string> func)
        {
            this.ClassTemplate = func(this.ClassTemplate);
            return this;
        }

        public ICodeFirst SettingConstructorTemplate(Func<string, string> func)
        {
            this.ConstructorTemplate = func(this.ConstructorTemplate);
            return this;
        }

        public ICodeFirst SettingPropertyDescriptionTemplate(Func<string, string> func)
        {
            this.PropertyDescriptionTemplate = func(this.PropertyDescriptionTemplate);
            return this;
        }

        public ICodeFirst SettingNamespaceTemplate(Func<string, string> func)
        {
            this.UsingTemplate = func(this.UsingTemplate);
            return this;
        }

        public ICodeFirst SettingPropertyTemplate(Func<string, string> func)
        {
            this.PropertyTemplate = func(this.PropertyTemplate);
            return this;
        }
        #endregion

        #region Setting Content
        public ICodeFirst IsCreateAttribute(bool isCreateAttribute = true)
        {
            this.IsAttribute = isCreateAttribute;
            return this;
        }
        public ICodeFirst IsCreateDefaultValue(bool isCreateDefaultValue = true)
        {
            this.IsDefaultValue = isCreateDefaultValue;
            return this;
        } 
        public ICodeFirst IsGuid2tringValue(bool isGuid2tring = true)
        {
            this.IsGuid2tring = isGuid2tring;
            return this;
        }
        public ICodeFirst IsSetEntitySuffix(string entitySuffix = "")
        {
            this.EntitySuffix = entitySuffix;
            return this;
        }
        #endregion

        #region Where
        public ICodeFirst Where(DbObjectType dbObjectType)
        {
            if (dbObjectType != DbObjectType.All)
                this.TableInfoList = this.TableInfoList.Where(it => it.DbObjectType == dbObjectType).ToList();
            return this;
        }

        public ICodeFirst Where(Func<string, bool> func)
        {
            this.TableInfoList = this.TableInfoList.Where(it => func(it.TableName)).ToList();
            return this;
        }

        public ICodeFirst WhereColumns(Func<string, bool> func)
        {
            WhereColumnsfunc = func;
            return this;
        }


        public ICodeFirst Where(params string[] objectNames)
        {
            if (objectNames.HasValue())
            {
                this.TableInfoList = this.TableInfoList.Where(it => objectNames.Select(x => x.ToLower()).Contains(it.TableName.ToLower())).ToList();
            }
            return this;
        }
        #endregion

        #region Core
        public Dictionary<string, string> ToClassStringList(string creator = "xx", string nameSpace = "Models")
        {
            this.Creator = creator;
            this.Namespace = nameSpace;
            Dictionary<string, string> result = new Dictionary<string, string>();
            if (this.TableInfoList.HasValue())
            {
                foreach (var tableInfo in this.TableInfoList)
                {
                    try
                    {
                        string classText = null;
                        string className = tableInfo.TableName;
                        classText = GetClassString(tableInfo, ref className);
                        result.Remove(className);
                        result.Add(className, classText);
                    }
                    catch (Exception ex)
                    {
                        Check.Exception(true, "Table '{0}' error,You can filter it with Db.CodeFirst.Where(name=>name!=\"{0}\" ) \r\n Error message:{1}", tableInfo.TableName, ex.Message);
                    }
                }
            }
            return result;
        }
        public Tuple<string, string> ToClassStringList(string tableName, string creator = "xx", string nameSpace = "Models")
        {
            this.Creator = creator;
            this.Namespace = nameSpace;
            string classText = ""; var className = tableName;
            var tableInfo = TableInfoList.Find(f => f.TableName.Equals(tableName, StringComparison.OrdinalIgnoreCase));
            if (tableInfo.IsNotNullOrEmpty())
            {
                try
                {
                    classText = GetClassString(tableInfo, ref className);
                }
                catch (Exception ex)
                {
                    Check.Exception(true, "Table '{0}' error,You can filter it with Db.CodeFirst.Where(name=>name!=\"{0}\" ) \r\n Error message:{1}", tableInfo.TableName, ex.Message);
                }
            }
            return Tuple.Create(className, classText);
        }

        internal string GetClassString(DbTableInfo tableInfo, ref string className)
        {
            string classText;
            var columns = this.Context.SqlDbProvider.GetColumnInfosByTableName(tableInfo.TableName, DbName);

            classText = this.ClassTemplate;
            string ConstructorText = IsDefaultValue ? this.ConstructorTemplate : null;
            className = Funs.GetEntityNameByTableName(EntitySuffix + tableInfo.TableName);
            classText = classText.Replace(CodeFirstTemplate.KeyClassName, className);
            classText = classText.Replace(CodeFirstTemplate.KeyTableName, tableInfo.TableName);
            classText = classText.Replace(CodeFirstTemplate.KeyCreator, Creator);
            classText = classText.Replace(CodeFirstTemplate.KeyNamespace, this.Namespace);
            classText = classText.Replace(CodeFirstTemplate.KeyUsing, IsAttribute ? (this.UsingTemplate + "using CNative.Dapper.Utils;\r\n") : this.UsingTemplate);
            classText = classText.Replace(CodeFirstTemplate.KeyClassDescription, this.ClassDescriptionTemplate.Replace(CodeFirstTemplate.KeyClassDescription, tableInfo.Description + "\r\n"));
            classText = classText.Replace(CodeFirstTemplate.KeyTableMap, IsAttribute ? string.Format(CodeFirstTemplate.ValueTableMap, tableInfo.TableName, DbName) : null);
            if (columns.HasValue())
            {
                foreach (var item in columns.Where(it => WhereColumnsfunc == null || WhereColumnsfunc(it.Name)))
                {
                    var isLast = columns.Last() == item;
                    var index = columns.IndexOf(item);
                    string PropertyText = this.PropertyTemplate;
                    string PropertyDescriptionText = this.PropertyDescriptionTemplate;
                    string propertyName = GetPropertyName(item);
                    string propertyTypeName = GetPropertyTypeName(item);
                    PropertyText = GetPropertyText(item, PropertyText);
                    PropertyDescriptionText = GetPropertyDescriptionText(item, PropertyDescriptionText);
                    PropertyText = PropertyDescriptionText + PropertyText;
                    classText = classText.Replace(CodeFirstTemplate.KeyPropertyName, PropertyText + (isLast ? "" : ("\r\n" + CodeFirstTemplate.KeyPropertyName)));
                    if (ConstructorText.HasValue() && item.DefaultValue != null)
                    {
                        var hasDefaultValue = columns.Skip(index + 1).Any(it => it.DefaultValue.HasValue());
                        ConstructorText = ConstructorText.Replace(CodeFirstTemplate.KeyPropertyName, propertyName);
                        ConstructorText = ConstructorText.Replace(CodeFirstTemplate.KeyDefaultValue, GetPropertyTypeConvert(item)) + (!hasDefaultValue ? "" : this.ConstructorTemplate);
                    }
                }
            }
            if (!columns.Any(it => it.DefaultValue != null))
            {
                ConstructorText = null;
            }
            classText = classText.Replace(CodeFirstTemplate.KeyConstructor, ConstructorText);
            classText = classText.Replace(CodeFirstTemplate.KeyPropertyName, null);
            return classText;
        }
        public void CreateClassFile(string directoryPath, string creator = "xx", string nameSpace = "Models")
        {
            var seChar = Path.DirectorySeparatorChar.ToString();
            Check.ArgumentNullException(directoryPath, "directoryPath can't null");
            var classStringList = ToClassStringList(creator, nameSpace);
            if (classStringList.IsValuable())
            {
                foreach (var item in classStringList)
                {
                    var filePath = directoryPath.TrimEnd('\\').TrimEnd('/') + string.Format(seChar + "{0}.cs", item.Key);
                    FileHelper.CreateFile(filePath, item.Value, Encoding.UTF8);
                }
            }
        }
        public void CreateClassFile(string directoryPath, string tableName, string creator = "xx", string nameSpace = "Models")
        {
            var seChar = Path.DirectorySeparatorChar.ToString();
            Check.ArgumentNullException(directoryPath, "directoryPath can't null");
            var classString = ToClassStringList(tableName, creator, nameSpace);
            if (classString.IsNotNullOrEmpty())
            {
                var filePath = directoryPath.TrimEnd('\\').TrimEnd('/') + string.Format(seChar + "{0}.cs", classString.Item1);
                FileHelper.CreateFile(filePath, classString.Item2, Encoding.UTF8);
            }
        }
        #endregion

        #region Private methods
        private string GetProertypeDefaultValue(DbColumnInfo item)
        {
            var result = item.DefaultValue;
            if (result == null) return null;
            if (Regex.IsMatch(result, @"^\(\'(.+)\'\)$"))
            {
                result = Regex.Match(result, @"^\(\'(.+)\'\)$").Groups[1].Value;
            }
            if (Regex.IsMatch(result, @"^\(\((.+)\)\)$"))
            {
                result = Regex.Match(result, @"^\(\((.+)\)\)$").Groups[1].Value;
            }
            if (Regex.IsMatch(result, @"^\((.+)\)$"))
            {
                result = Regex.Match(result, @"^\((.+)\)$").Groups[1].Value;
            }
            if (result.Equals(this.Context.SqlDbProvider.SqlDateNow, StringComparison.CurrentCultureIgnoreCase))
            {
                result = "DateTime.Now";
            }
            result = result.Replace("\r", "\t").Replace("\n", "\t");
            result = result.IsIn("''", "\"\"") ? string.Empty : result;
            return result;
        }
        private string GetPropertyText(DbColumnInfo item, string PropertyText)
        {
            string DbFieldMapText = CodeFirstTemplate.ValueSugarCoulmn;
            var propertyName = GetPropertyName(item);
            var isMappingColumn = propertyName != item.Name;
            var hasDbFieldMap = item.IsPrimaryKey == true || item.IsIdentity == true || isMappingColumn;
            if (hasDbFieldMap && this.IsAttribute)
            {
                List<string> joinList = new List<string>();
                if (item.IsPrimaryKey)
                {
                    joinList.Add("IsPrimaryKey=true");
                }
                if (item.IsIdentity)
                {
                    joinList.Add("IsIdentity=true");
                }
                if (isMappingColumn)
                {
                    joinList.Add("ColumnName=\"" + item.Name + "\"");
                }
                DbFieldMapText = string.Format(DbFieldMapText, string.Join(",", joinList));
            }
            else
            {
                DbFieldMapText = null;
            }
            string typeString = GetPropertyTypeName(item);
            PropertyText = PropertyText.Replace(CodeFirstTemplate.KeyDbFieldMap, DbFieldMapText);
            PropertyText = PropertyText.Replace(CodeFirstTemplate.KeyPropertyType, typeString);
            PropertyText = PropertyText.Replace(CodeFirstTemplate.KeyPropertyName, propertyName);
            return PropertyText;
        }

        private string GetPropertyName(DbColumnInfo item)
        {
            return item.Name;
        }
        private string GetPropertyTypeName(DbColumnInfo item)
        {
            string result = this.Context.SqlDbProvider.GetCsharpTypeName(item.Type);
            if (result != "string" && result != "byte[]" && result != "object" && item.IsNullable)
            {
                result += "?";
            }
            else if (result == "Int32")
            {
                result = "int";
            }
            else if (result == "String")
            {
                result = "string";
            }
            else if (IsGuid2tring && result == "Guid")
            {
                result = "string";
            }
            return result;
        }
        private string GetPropertyTypeConvert(DbColumnInfo item)
        {
            var convertString = GetProertypeDefaultValue(item);
            if (convertString == "DateTime.Now" || convertString == null)
                return convertString;
            if (convertString.ObjToString() == "newid()")
            {
                return "Guid.NewGuid()";
            }
            if (item.Type == "bit")
                return (convertString == "1" || convertString.Equals("true", StringComparison.CurrentCultureIgnoreCase)).ToString().ToLower();
            string result = this.Context.SqlDbProvider.GetConvertString(item.Type) + "(\"" + convertString + "\")";
            return result;
        }
        private string GetPropertyDescriptionText(DbColumnInfo item, string propertyDescriptionText)
        {
            propertyDescriptionText = propertyDescriptionText.Replace(CodeFirstTemplate.KeyPropertyDescription, GetColumnDescription(item.Description));
            propertyDescriptionText = propertyDescriptionText.Replace(CodeFirstTemplate.KeyDefaultValue, GetProertypeDefaultValue(item));
            propertyDescriptionText = propertyDescriptionText.Replace(CodeFirstTemplate.KeyIsNullable, item.IsNullable.ObjToString());
            return propertyDescriptionText;
        }
        private string GetColumnDescription(string columnDescription)
        {
            if (columnDescription == null) return columnDescription;
            columnDescription = columnDescription.Replace("\r", "\t");
            columnDescription = columnDescription.Replace("\n", "\t");
            columnDescription = Regex.Replace(columnDescription, "\t{2,}", "\t");
            return columnDescription;
        }
        #endregion
    }
    #endregion
    #region SqlServerCodeFirst
    public class SqlServerCodeFirst : CodeFirstProvider
    {
        public SqlServerCodeFirst() : base()
        {
        }
        public SqlServerCodeFirst(DbHelper _Context) : base(_Context)
        {
        }
        public SqlServerCodeFirst(string dbName) : base(dbName)
        {
        }
    }
    public class OracleCodeFirst : CodeFirstProvider
    {
        public OracleCodeFirst() : base()
        {
        }
        public OracleCodeFirst(DbHelper _Context) : base(_Context)
        {
        }
        public OracleCodeFirst(string dbName) : base(dbName)
        {
        }
    }
    public class MySqlCodeFirst : CodeFirstProvider
    {
        public MySqlCodeFirst() : base()
        {
        }
        public MySqlCodeFirst(DbHelper _Context) : base(_Context)
        {
        }
        public MySqlCodeFirst(string dbName) : base(dbName)
        {
        }
    }
    public class SqliteCodeFirst : CodeFirstProvider
    {
        public SqliteCodeFirst() : base()
        {
        }
        public SqliteCodeFirst(DbHelper _Context) : base(_Context)
        {
        }
        public SqliteCodeFirst(string dbName) : base(dbName)
        {
        }
    }
    public class PostgreSQLCodeFirst : CodeFirstProvider
    {
        public PostgreSQLCodeFirst() : base()
        {
        }
        public PostgreSQLCodeFirst(DbHelper _Context) : base(_Context)
        {
        }
        public PostgreSQLCodeFirst(string dbName) : base(dbName)
        {
        }
    }
    #endregion
}
